/*! @azure/msal-common v7.6.0 2022-10-10 */
'use strict';
import { __extends, __awaiter, __generator, __assign, __spreadArrays } from '../_virtual/_tslib.js';
import { BaseClient } from './BaseClient.js';
import { RequestParameterBuilder } from '../request/RequestParameterBuilder.js';
import { Separators, AADServerParamKeys, AuthenticationScheme, GrantType, PromptValue, HeaderNames } from '../utils/Constants.js';
import { ResponseHandler } from '../response/ResponseHandler.js';
import { StringUtils } from '../utils/StringUtils.js';
import { ClientAuthError } from '../error/ClientAuthError.js';
import { UrlString } from '../url/UrlString.js';
import { PopTokenGenerator } from '../crypto/PopTokenGenerator.js';
import { TimeUtils } from '../utils/TimeUtils.js';
import { buildClientInfo, buildClientInfoFromHomeAccountId } from '../account/ClientInfo.js';
import { CcsCredentialType } from '../account/CcsCredential.js';
import { ClientConfigurationError } from '../error/ClientConfigurationError.js';
import { RequestValidator } from '../request/RequestValidator.js';

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */
/**
 * Oauth2.0 Authorization Code client
 */
var AuthorizationCodeClient = /** @class */ (function (_super) {
    __extends(AuthorizationCodeClient, _super);
    function AuthorizationCodeClient(configuration) {
        var _this = _super.call(this, configuration) || this;
        // Flag to indicate if client is for hybrid spa auth code redemption
        _this.includeRedirectUri = true;
        return _this;
    }
    /**
     * Creates the URL of the authorization request letting the user input credentials and consent to the
     * application. The URL target the /authorize endpoint of the authority configured in the
     * application object.
     *
     * Once the user inputs their credentials and consents, the authority will send a response to the redirect URI
     * sent in the request and should contain an authorization code, which can then be used to acquire tokens via
     * acquireToken(AuthorizationCodeRequest)
     * @param request
     */
    AuthorizationCodeClient.prototype.getAuthCodeUrl = function (request) {
        return __awaiter(this, void 0, void 0, function () {
            var queryString;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.createAuthCodeUrlQueryString(request)];
                    case 1:
                        queryString = _a.sent();
                        return [2 /*return*/, UrlString.appendQueryString(this.authority.authorizationEndpoint, queryString)];
                }
            });
        });
    };
    /**
     * API to acquire a token in exchange of 'authorization_code` acquired by the user in the first leg of the
     * authorization_code_grant
     * @param request
     */
    AuthorizationCodeClient.prototype.acquireToken = function (request, authCodePayload) {
        var _a;
        return __awaiter(this, void 0, void 0, function () {
            var reqTimestamp, response, requestId, responseHandler;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        this.logger.info("in acquireToken call");
                        if (!request || StringUtils.isEmpty(request.code)) {
                            throw ClientAuthError.createTokenRequestCannotBeMadeError();
                        }
                        reqTimestamp = TimeUtils.nowSeconds();
                        return [4 /*yield*/, this.executeTokenRequest(this.authority, request)];
                    case 1:
                        response = _b.sent();
                        requestId = (_a = response.headers) === null || _a === void 0 ? void 0 : _a[HeaderNames.X_MS_REQUEST_ID];
                        responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheManager, this.cryptoUtils, this.logger, this.config.serializableCache, this.config.persistencePlugin);
                        // Validate response. This function throws a server error if an error is returned by the server.
                        responseHandler.validateTokenResponse(response.body);
                        return [4 /*yield*/, responseHandler.handleServerTokenResponse(response.body, this.authority, reqTimestamp, request, authCodePayload, undefined, undefined, undefined, requestId)];
                    case 2: return [2 /*return*/, _b.sent()];
                }
            });
        });
    };
    /**
     * Handles the hash fragment response from public client code request. Returns a code response used by
     * the client to exchange for a token in acquireToken.
     * @param hashFragment
     */
    AuthorizationCodeClient.prototype.handleFragmentResponse = function (hashFragment, cachedState) {
        // Handle responses.
        var responseHandler = new ResponseHandler(this.config.authOptions.clientId, this.cacheManager, this.cryptoUtils, this.logger, null, null);
        // Deserialize hash fragment response parameters.
        var hashUrlString = new UrlString(hashFragment);
        // Deserialize hash fragment response parameters.
        var serverParams = UrlString.getDeserializedHash(hashUrlString.getHash());
        // Get code response
        responseHandler.validateServerAuthorizationCodeResponse(serverParams, cachedState, this.cryptoUtils);
        // throw when there is no auth code in the response
        if (!serverParams.code) {
            throw ClientAuthError.createNoAuthCodeInServerResponseError();
        }
        return __assign(__assign({}, serverParams), { 
            // Code param is optional in ServerAuthorizationCodeResponse but required in AuthorizationCodePaylod
            code: serverParams.code });
    };
    /**
     * Used to log out the current user, and redirect the user to the postLogoutRedirectUri.
     * Default behaviour is to redirect the user to `window.location.href`.
     * @param authorityUri
     */
    AuthorizationCodeClient.prototype.getLogoutUri = function (logoutRequest) {
        // Throw error if logoutRequest is null/undefined
        if (!logoutRequest) {
            throw ClientConfigurationError.createEmptyLogoutRequestError();
        }
        var queryString = this.createLogoutUrlQueryString(logoutRequest);
        // Construct logout URI
        return UrlString.appendQueryString(this.authority.endSessionEndpoint, queryString);
    };
    /**
     * Executes POST request to token endpoint
     * @param authority
     * @param request
     */
    AuthorizationCodeClient.prototype.executeTokenRequest = function (authority, request) {
        return __awaiter(this, void 0, void 0, function () {
            var thumbprint, requestBody, queryParameters, ccsCredential, clientInfo, headers, endpoint;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        thumbprint = {
                            clientId: this.config.authOptions.clientId,
                            authority: authority.canonicalAuthority,
                            scopes: request.scopes,
                            claims: request.claims,
                            authenticationScheme: request.authenticationScheme,
                            resourceRequestMethod: request.resourceRequestMethod,
                            resourceRequestUri: request.resourceRequestUri,
                            shrClaims: request.shrClaims,
                            sshKid: request.sshKid
                        };
                        return [4 /*yield*/, this.createTokenRequestBody(request)];
                    case 1:
                        requestBody = _a.sent();
                        queryParameters = this.createTokenQueryParameters(request);
                        ccsCredential = undefined;
                        if (request.clientInfo) {
                            try {
                                clientInfo = buildClientInfo(request.clientInfo, this.cryptoUtils);
                                ccsCredential = {
                                    credential: "" + clientInfo.uid + Separators.CLIENT_INFO_SEPARATOR + clientInfo.utid,
                                    type: CcsCredentialType.HOME_ACCOUNT_ID
                                };
                            }
                            catch (e) {
                                this.logger.verbose("Could not parse client info for CCS Header: " + e);
                            }
                        }
                        headers = this.createTokenRequestHeaders(ccsCredential || request.ccsCredential);
                        endpoint = StringUtils.isEmpty(queryParameters) ? authority.tokenEndpoint : authority.tokenEndpoint + "?" + queryParameters;
                        return [2 /*return*/, this.executePostToTokenEndpoint(endpoint, requestBody, headers, thumbprint)];
                }
            });
        });
    };
    /**
     * Creates query string for the /token request
     * @param request
     */
    AuthorizationCodeClient.prototype.createTokenQueryParameters = function (request) {
        var parameterBuilder = new RequestParameterBuilder();
        if (request.tokenQueryParameters) {
            parameterBuilder.addExtraQueryParameters(request.tokenQueryParameters);
        }
        return parameterBuilder.createQueryString();
    };
    /**
     * Generates a map for all the params to be sent to the service
     * @param request
     */
    AuthorizationCodeClient.prototype.createTokenRequestBody = function (request) {
        return __awaiter(this, void 0, void 0, function () {
            var parameterBuilder, clientAssertion, popTokenGenerator, reqCnfData, correlationId, ccsCred, clientInfo, clientInfo;
            var _a;
            return __generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        parameterBuilder = new RequestParameterBuilder();
                        parameterBuilder.addClientId(this.config.authOptions.clientId);
                        /*
                         * For hybrid spa flow, there will be a code but no verifier
                         * In this scenario, don't include redirect uri as auth code will not be bound to redirect URI
                         */
                        if (!this.includeRedirectUri) {
                            // Just validate
                            RequestValidator.validateRedirectUri(request.redirectUri);
                        }
                        else {
                            // Validate and include redirect uri
                            parameterBuilder.addRedirectUri(request.redirectUri);
                        }
                        // Add scope array, parameter builder will add default scopes and dedupe
                        parameterBuilder.addScopes(request.scopes);
                        // add code: user set, not validated
                        parameterBuilder.addAuthorizationCode(request.code);
                        // Add library metadata
                        parameterBuilder.addLibraryInfo(this.config.libraryInfo);
                        parameterBuilder.addApplicationTelemetry(this.config.telemetry.application);
                        parameterBuilder.addThrottling();
                        if (this.serverTelemetryManager) {
                            parameterBuilder.addServerTelemetry(this.serverTelemetryManager);
                        }
                        // add code_verifier if passed
                        if (request.codeVerifier) {
                            parameterBuilder.addCodeVerifier(request.codeVerifier);
                        }
                        if (this.config.clientCredentials.clientSecret) {
                            parameterBuilder.addClientSecret(this.config.clientCredentials.clientSecret);
                        }
                        if (this.config.clientCredentials.clientAssertion) {
                            clientAssertion = this.config.clientCredentials.clientAssertion;
                            parameterBuilder.addClientAssertion(clientAssertion.assertion);
                            parameterBuilder.addClientAssertionType(clientAssertion.assertionType);
                        }
                        parameterBuilder.addGrantType(GrantType.AUTHORIZATION_CODE_GRANT);
                        parameterBuilder.addClientInfo();
                        if (!(request.authenticationScheme === AuthenticationScheme.POP)) return [3 /*break*/, 2];
                        popTokenGenerator = new PopTokenGenerator(this.cryptoUtils);
                        return [4 /*yield*/, popTokenGenerator.generateCnf(request)];
                    case 1:
                        reqCnfData = _b.sent();
                        // SPA PoP requires full Base64Url encoded req_cnf string (unhashed)
                        parameterBuilder.addPopToken(reqCnfData.reqCnfString);
                        return [3 /*break*/, 3];
                    case 2:
                        if (request.authenticationScheme === AuthenticationScheme.SSH) {
                            if (request.sshJwk) {
                                parameterBuilder.addSshJwk(request.sshJwk);
                            }
                            else {
                                throw ClientConfigurationError.createMissingSshJwkError();
                            }
                        }
                        _b.label = 3;
                    case 3:
                        correlationId = request.correlationId || this.config.cryptoInterface.createNewGuid();
                        parameterBuilder.addCorrelationId(correlationId);
                        if (!StringUtils.isEmptyObj(request.claims) || this.config.authOptions.clientCapabilities && this.config.authOptions.clientCapabilities.length > 0) {
                            parameterBuilder.addClaims(request.claims, this.config.authOptions.clientCapabilities);
                        }
                        ccsCred = undefined;
                        if (request.clientInfo) {
                            try {
                                clientInfo = buildClientInfo(request.clientInfo, this.cryptoUtils);
                                ccsCred = {
                                    credential: "" + clientInfo.uid + Separators.CLIENT_INFO_SEPARATOR + clientInfo.utid,
                                    type: CcsCredentialType.HOME_ACCOUNT_ID
                                };
                            }
                            catch (e) {
                                this.logger.verbose("Could not parse client info for CCS Header: " + e);
                            }
                        }
                        else {
                            ccsCred = request.ccsCredential;
                        }
                        // Adds these as parameters in the request instead of headers to prevent CORS preflight request
                        if (this.config.systemOptions.preventCorsPreflight && ccsCred) {
                            switch (ccsCred.type) {
                                case CcsCredentialType.HOME_ACCOUNT_ID:
                                    try {
                                        clientInfo = buildClientInfoFromHomeAccountId(ccsCred.credential);
                                        parameterBuilder.addCcsOid(clientInfo);
                                    }
                                    catch (e) {
                                        this.logger.verbose("Could not parse home account ID for CCS Header: " + e);
                                    }
                                    break;
                                case CcsCredentialType.UPN:
                                    parameterBuilder.addCcsUpn(ccsCred.credential);
                                    break;
                            }
                        }
                        if (request.tokenBodyParameters) {
                            parameterBuilder.addExtraQueryParameters(request.tokenBodyParameters);
                        }
                        // Add hybrid spa parameters if not already provided
                        if (request.enableSpaAuthorizationCode && (!request.tokenBodyParameters || !request.tokenBodyParameters[AADServerParamKeys.RETURN_SPA_CODE])) {
                            parameterBuilder.addExtraQueryParameters((_a = {},
                                _a[AADServerParamKeys.RETURN_SPA_CODE] = "1",
                                _a));
                        }
                        return [2 /*return*/, parameterBuilder.createQueryString()];
                }
            });
        });
    };
    /**
     * This API validates the `AuthorizationCodeUrlRequest` and creates a URL
     * @param request
     */
    AuthorizationCodeClient.prototype.createAuthCodeUrlQueryString = function (request) {
        return __awaiter(this, void 0, void 0, function () {
            var parameterBuilder, requestScopes, correlationId, accountSid, accountLoginHintClaim, clientInfo, clientInfo, clientInfo, popTokenGenerator, reqCnfData;
            return __generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        parameterBuilder = new RequestParameterBuilder();
                        parameterBuilder.addClientId(this.config.authOptions.clientId);
                        requestScopes = __spreadArrays(request.scopes || [], request.extraScopesToConsent || []);
                        parameterBuilder.addScopes(requestScopes);
                        // validate the redirectUri (to be a non null value)
                        parameterBuilder.addRedirectUri(request.redirectUri);
                        correlationId = request.correlationId || this.config.cryptoInterface.createNewGuid();
                        parameterBuilder.addCorrelationId(correlationId);
                        // add response_mode. If not passed in it defaults to query.
                        parameterBuilder.addResponseMode(request.responseMode);
                        // add response_type = code
                        parameterBuilder.addResponseTypeCode();
                        // add library info parameters
                        parameterBuilder.addLibraryInfo(this.config.libraryInfo);
                        parameterBuilder.addApplicationTelemetry(this.config.telemetry.application);
                        // add client_info=1
                        parameterBuilder.addClientInfo();
                        if (request.codeChallenge && request.codeChallengeMethod) {
                            parameterBuilder.addCodeChallengeParams(request.codeChallenge, request.codeChallengeMethod);
                        }
                        if (request.prompt) {
                            parameterBuilder.addPrompt(request.prompt);
                        }
                        if (request.domainHint) {
                            parameterBuilder.addDomainHint(request.domainHint);
                        }
                        // Add sid or loginHint with preference for login_hint claim (in request) -> sid -> loginHint (upn/email) -> username of AccountInfo object
                        if (request.prompt !== PromptValue.SELECT_ACCOUNT) {
                            // AAD will throw if prompt=select_account is passed with an account hint
                            if (request.sid && request.prompt === PromptValue.NONE) {
                                // SessionID is only used in silent calls
                                this.logger.verbose("createAuthCodeUrlQueryString: Prompt is none, adding sid from request");
                                parameterBuilder.addSid(request.sid);
                            }
                            else if (request.account) {
                                accountSid = this.extractAccountSid(request.account);
                                accountLoginHintClaim = this.extractLoginHint(request.account);
                                // If login_hint claim is present, use it over sid/username
                                if (accountLoginHintClaim) {
                                    this.logger.verbose("createAuthCodeUrlQueryString: login_hint claim present on account");
                                    parameterBuilder.addLoginHint(accountLoginHintClaim);
                                    try {
                                        clientInfo = buildClientInfoFromHomeAccountId(request.account.homeAccountId);
                                        parameterBuilder.addCcsOid(clientInfo);
                                    }
                                    catch (e) {
                                        this.logger.verbose("createAuthCodeUrlQueryString: Could not parse home account ID for CCS Header");
                                    }
                                }
                                else if (accountSid && request.prompt === PromptValue.NONE) {
                                    /*
                                     * If account and loginHint are provided, we will check account first for sid before adding loginHint
                                     * SessionId is only used in silent calls
                                     */
                                    this.logger.verbose("createAuthCodeUrlQueryString: Prompt is none, adding sid from account");
                                    parameterBuilder.addSid(accountSid);
                                    try {
                                        clientInfo = buildClientInfoFromHomeAccountId(request.account.homeAccountId);
                                        parameterBuilder.addCcsOid(clientInfo);
                                    }
                                    catch (e) {
                                        this.logger.verbose("createAuthCodeUrlQueryString: Could not parse home account ID for CCS Header");
                                    }
                                }
                                else if (request.loginHint) {
                                    this.logger.verbose("createAuthCodeUrlQueryString: Adding login_hint from request");
                                    parameterBuilder.addLoginHint(request.loginHint);
                                    parameterBuilder.addCcsUpn(request.loginHint);
                                }
                                else if (request.account.username) {
                                    // Fallback to account username if provided
                                    this.logger.verbose("createAuthCodeUrlQueryString: Adding login_hint from account");
                                    parameterBuilder.addLoginHint(request.account.username);
                                    try {
                                        clientInfo = buildClientInfoFromHomeAccountId(request.account.homeAccountId);
                                        parameterBuilder.addCcsOid(clientInfo);
                                    }
                                    catch (e) {
                                        this.logger.verbose("createAuthCodeUrlQueryString: Could not parse home account ID for CCS Header");
                                    }
                                }
                            }
                            else if (request.loginHint) {
                                this.logger.verbose("createAuthCodeUrlQueryString: No account, adding login_hint from request");
                                parameterBuilder.addLoginHint(request.loginHint);
                                parameterBuilder.addCcsUpn(request.loginHint);
                            }
                        }
                        else {
                            this.logger.verbose("createAuthCodeUrlQueryString: Prompt is select_account, ignoring account hints");
                        }
                        if (request.nonce) {
                            parameterBuilder.addNonce(request.nonce);
                        }
                        if (request.state) {
                            parameterBuilder.addState(request.state);
                        }
                        if (!StringUtils.isEmpty(request.claims) || this.config.authOptions.clientCapabilities && this.config.authOptions.clientCapabilities.length > 0) {
                            parameterBuilder.addClaims(request.claims, this.config.authOptions.clientCapabilities);
                        }
                        if (request.extraQueryParameters) {
                            parameterBuilder.addExtraQueryParameters(request.extraQueryParameters);
                        }
                        if (!request.nativeBroker) return [3 /*break*/, 2];
                        // signal ests that this is a WAM call
                        parameterBuilder.addNativeBroker();
                        if (!(request.authenticationScheme === AuthenticationScheme.POP)) return [3 /*break*/, 2];
                        popTokenGenerator = new PopTokenGenerator(this.cryptoUtils);
                        return [4 /*yield*/, popTokenGenerator.generateCnf(request)];
                    case 1:
                        reqCnfData = _a.sent();
                        parameterBuilder.addPopToken(reqCnfData.reqCnfHash);
                        _a.label = 2;
                    case 2: return [2 /*return*/, parameterBuilder.createQueryString()];
                }
            });
        });
    };
    /**
     * This API validates the `EndSessionRequest` and creates a URL
     * @param request
     */
    AuthorizationCodeClient.prototype.createLogoutUrlQueryString = function (request) {
        var parameterBuilder = new RequestParameterBuilder();
        if (request.postLogoutRedirectUri) {
            parameterBuilder.addPostLogoutRedirectUri(request.postLogoutRedirectUri);
        }
        if (request.correlationId) {
            parameterBuilder.addCorrelationId(request.correlationId);
        }
        if (request.idTokenHint) {
            parameterBuilder.addIdTokenHint(request.idTokenHint);
        }
        if (request.state) {
            parameterBuilder.addState(request.state);
        }
        if (request.logoutHint) {
            parameterBuilder.addLogoutHint(request.logoutHint);
        }
        if (request.extraQueryParameters) {
            parameterBuilder.addExtraQueryParameters(request.extraQueryParameters);
        }
        return parameterBuilder.createQueryString();
    };
    /**
     * Helper to get sid from account. Returns null if idTokenClaims are not present or sid is not present.
     * @param account
     */
    AuthorizationCodeClient.prototype.extractAccountSid = function (account) {
        var _a;
        return ((_a = account.idTokenClaims) === null || _a === void 0 ? void 0 : _a.sid) || null;
    };
    AuthorizationCodeClient.prototype.extractLoginHint = function (account) {
        var _a;
        return ((_a = account.idTokenClaims) === null || _a === void 0 ? void 0 : _a.login_hint) || null;
    };
    return AuthorizationCodeClient;
}(BaseClient));

export { AuthorizationCodeClient };
//# sourceMappingURL=AuthorizationCodeClient.js.map
